package com.example.admin.bilibiliijkplayer.services;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.os.IBinder;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.View;

import com.example.admin.bilibiliijkplayer.interfaces.IAudioPlayer;
import com.example.admin.bilibiliijkplayer.interfaces.IAudioPlayerControl;
import com.example.admin.bilibiliijkplayer.interfaces.IAudioService;
import com.example.admin.bilibiliijkplayer.interfaces.IAudioServiceCallback;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * 项目名称  ：Bilibili_ijkplayer
 * 类名称    ：
 * 类描述    ：
 * 创建人    ：Aman/862032054@qq.com
 * 创建时间  ：2016/10/19 13:59
 * 修改人    ：admin
 * 修改时间  ：2016/10/19 13:59
 * 修改备注  ：
 *
 * @version V1.0
 */
public class AudioServiceController implements IAudioPlayerControl {
    private final static String TAG = AudioServiceController.class.getName();
    private static boolean mIsBound = false;
    private IAudioService mAudioServiceBinder;
    private ServiceConnection mAudioServiceConnection;
    private View mBindView;

    private final IAudioServiceCallback mCallback = new IAudioServiceCallback.Stub() {
        @Override
        public void update() throws RemoteException {
            updateAudioPlayer();
        }

        @Override
        public void onCompletion() throws RemoteException {
            updateAudioPlayerCompletion();
        }

        @Override
        public void onError(int i, int i1) throws RemoteException {
            updateAudioPlayerError(i, i1);
        }

    };
    private final ArrayList<IAudioPlayer> mAudioPlayer;

    private AudioServiceController() {
        mAudioPlayer = new ArrayList<IAudioPlayer>();
    }

    private static AudioServiceController mInstance;

    public static AudioServiceController getInstance() {
        if (mInstance == null) {
            mInstance = new AudioServiceController();
        }
        return mInstance;
    }

    /**
     * Bind to audio service if it is running
     */
    public void bindAudioService(Context context) {
        if (context == null) {
            Log.w(TAG, "bindAudioService() with null Context. Ooops");
            return;
        }
        context = context.getApplicationContext();

        if (!mIsBound) {
            Intent service = new Intent(context, AudioService.class);

            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
            final boolean enableHS = prefs.getBoolean("enable_headset_detection", true);

            // Setup audio service connection
            mAudioServiceConnection = new ServiceConnection() {
                @Override
                public void onServiceDisconnected(ComponentName name) {
                    Log.d(TAG, "Service Disconnected");
                    mAudioServiceBinder = null;
                    mIsBound = false;
                }

                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
                    if (!mIsBound) // Can happen if unbind is called quickly before this callback
                        return;
                    Log.d(TAG, "Service Connected");
                    mAudioServiceBinder = IAudioService.Stub.asInterface(service);

                    // Register controller to the service
                    try {
                        mAudioServiceBinder.addAudioCallback(mCallback);
                        mAudioServiceBinder.detectHeadset(enableHS);
                    } catch (RemoteException e) {
                        Log.e(TAG, "remote procedure call failed: addAudioCallback()");
                    }
                    updateAudioPlayer();
                }
            };

            mIsBound = context.bindService(service, mAudioServiceConnection, Context.BIND_AUTO_CREATE);
        } else {
            // Register controller to the service
            try {
                if (mAudioServiceBinder != null)
                    mAudioServiceBinder.addAudioCallback(mCallback);
            } catch (RemoteException e) {
                Log.e(TAG, "remote procedure call failed: addAudioCallback()");
            }
        }
    }

    public void unbindAudioService(Context context) {
        if (context == null) {
            Log.w(TAG, "unbindAudioService() with null Context. Ooops");
            return;
        }
        context = context.getApplicationContext();

        if (mIsBound) {
            mIsBound = false;
            try {
                if (mAudioServiceBinder != null)
                    mAudioServiceBinder.removeAudioCallback(mCallback);
            } catch (RemoteException e) {
                Log.e(TAG, "remote procedure call failed: removeAudioCallback()");
            }
            context.unbindService(mAudioServiceConnection);
            mAudioServiceBinder = null;
            mAudioServiceConnection = null;
        }
    }

    /**
     * Add a AudioPlayer
     *
     * @param ap
     */
    public void addAudioPlayer(IAudioPlayer ap) {
        if (!mAudioPlayer.contains(ap))
            mAudioPlayer.add(ap);
    }

    /**
     * Remove AudioPlayer from list
     *
     * @param ap
     */
    public void removeAudioPlayer(IAudioPlayer ap) {
        if (mAudioPlayer.contains(ap))
            mAudioPlayer.remove(ap);
    }

    /**
     * Update all AudioPlayer
     */
    private void updateAudioPlayer() {
        for (IAudioPlayer player : mAudioPlayer)
            player.update(mBindView);
    }

    /**
     * Update all AudioPlayer
     */
    private void updateAudioPlayerCompletion() {
        for (IAudioPlayer player : mAudioPlayer)
            player.onCompletion(mBindView);
    }

    /**
     * Update all AudioPlayer
     */
    private void updateAudioPlayerError(int i, int i1) {
        for (IAudioPlayer player : mAudioPlayer)
            player.onError(mBindView, i, i1);
    }

    /**
     * This is a handy utility function to call remote procedure calls from mAudioServiceBinder
     * to reduce code duplication across methods of AudioServiceController.
     *
     * @param instance       The instance of IAudioService to call, usually mAudioServiceBinder
     * @param returnType     Return type of the method being called
     * @param defaultValue   Default value to return in case of null or exception
     * @param functionName   The function name to call, e.g. "stop"
     * @param parameterTypes List of parameter types. Pass null if none.
     * @param parameters     List of parameters. Must be in same order as parameterTypes. Pass null if none.
     * @return The results of the RPC or defaultValue if error
     */
    private <T> T remoteProcedureCall(IAudioService instance, Class<T> returnType, T defaultValue, String functionName, Class<?> parameterTypes[], Object parameters[]) {
        if (instance == null) {
            return defaultValue;
        }
        try {
            Method m = IAudioService.class.getMethod(functionName, parameterTypes);
            @SuppressWarnings("unchecked")
            T returnVal = (T) m.invoke(instance, parameters);
            return returnVal;
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
            return defaultValue;
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
            return defaultValue;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            return defaultValue;
        } catch (InvocationTargetException e) {
            if (e.getTargetException() instanceof RemoteException) {
                Log.e(TAG, "remote procedure call failed: " + functionName + "()");
            }
            return defaultValue;
        }
    }

    @Override
    public long getLength() {
        return remoteProcedureCall(mAudioServiceBinder, long.class, 0L, "getLength", null, null);
    }

    @Override
    public long getTime() {
        return remoteProcedureCall(mAudioServiceBinder, long.class, 0L, "getTime", null, null);
    }

    @Override
    public void seekTo(long time) {
        remoteProcedureCall(mAudioServiceBinder, Void.class, (Void) null, "seekTo", new Class<?>[]{long.class}, new Object[]{time});
    }

    @Override
    public void play() {
        remoteProcedureCall(mAudioServiceBinder, Void.class, (Void) null, "play", null, null);
    }

    @Override
    public void pause() {
        remoteProcedureCall(mAudioServiceBinder, Void.class, (Void) null, "pause", null, null);
    }

    //TODO
    @Override
    public void stop() {
        remoteProcedureCall(mAudioServiceBinder, Void.class, (Void) null, "stop", null, null);
    }

    @Override
    public boolean isPlaying() {
        return /*hasMedia() &&*/ remoteProcedureCall(mAudioServiceBinder, boolean.class, false, "isPlaying", null, null);
    }

    @Override
    public String getCurrentMediaLocation() {
        return remoteProcedureCall(mAudioServiceBinder, String.class, null, "getCurrentMediaLocation", null, null);
    }

    @Override
    public void load(List<String> mediaPathList, int position, View bindView) {
        mBindView = bindView;
        remoteProcedureCall(mAudioServiceBinder, Void.class, (Void) null, "load", new Class<?>[]{List.class, int.class}, new Object[]{mediaPathList, position});
    }


}
